package com.googlecode.pacswitch;

import java.io.File;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.CompletionHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy;
import java.util.concurrent.TimeUnit;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.xml.sax.SAXException;

/**
 * @author Andrey Pavlenko
 */
public class Main {

    @SuppressWarnings("unchecked")
    public static void main(final String[] args) throws Exception {
        if (args.length == 0) {
            System.err.println("No configuration file specified");
            System.exit(1);
        }

        final File configFile = new File(args[0]);
        final Schema schema = loadSchema(configFile.getParentFile());
        final JAXBContext ctx = JAXBContext.newInstance(loadRegistry());
        final Unmarshaller um = ctx.createUnmarshaller();
        um.setSchema(schema);
        // um.setEventHandler(new
        // javax.xml.bind.helpers.DefaultValidationEventHandler());
        final Config config = ((JAXBElement<Config>) um.unmarshal(configFile))
                .getValue();

        final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1,
                Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>(), new CallerRunsPolicy());
        final AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup
                .withCachedThreadPool(threadPool, 1);
        threadPool.prestartCoreThread();
        threadPool.setMaximumPoolSize(3);

        final PacSwitch ps = new PacSwitch(config, channelGroup);
        ps.addShutdownListener(new ShutdownHandler());
        start(ps);
    }

    private static void start(final PacSwitch ps) {
        try {
            ps.start();
        } catch (final Throwable ex) {
            ps.getConfig().getLog().error("Startup failed. Exiting.", ex);
            System.exit(1);
        }
    }

    private static Schema loadSchema(final File dir) throws SAXException {
        final ArrayList<Source> sources = new ArrayList<>();
        final File[] files = dir.listFiles();

        if (files != null) {

            for (final File f : files) {
                if (f.getName().endsWith(".xsd")) {
                    sources.add(new StreamSource(f));
                }
            }
        }

        return SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
                .newSchema(sources.toArray(new Source[sources.size()]));
    }

    private static Class<?>[] loadRegistry() {
        final List<Class<?>> classes = new ArrayList<>();

        for (final ConfigRegistry r : ServiceLoader.load(ConfigRegistry.class)) {
            classes.add(r.getClass());
        }

        return classes.toArray(new Class[classes.size()]);
    }

    private static final class ShutdownHandler implements
            CompletionHandler<Boolean, PacSwitch> {

        @Override
        public void completed(final Boolean result, final PacSwitch ps) {
            ps.getConfig().getLog().warn("Unexpected completion");
            System.exit(1);
        }

        @Override
        public void failed(final Throwable exc, final PacSwitch ps) {
            ps.getConfig().getLog().warn("Restarting due to error", exc);
            start(ps);
        }
    }
}
